Категории
Самые читаемые
onlinekniga.com » Компьютеры и Интернет » Прочая околокомпьтерная литература » Многопоточное программирование в Java - Тимур Машнин

Многопоточное программирование в Java - Тимур Машнин

Читать онлайн Многопоточное программирование в Java - Тимур Машнин

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 2 3 4 5 6 7 8 9 10 ... 15
Перейти на страницу:
ожидает блокировки.

Запрашивать блокировку, если она свободна в течение заданного времени ожидания, и, если текущий поток не был прерван. С простой синхронизацией невозможно пытаться получить блокировку, не будучи готовым к долгому ожиданию.

Возможность одной блокировки иметь несколько условий, чтобы ждать или пробуждаться.

Так как в случае интерфейса Lock, блокировка представлена объектом, это дает возможность повторно используемой блокировки, а также возможность освобождать и приобретать блокировки в любом порядке, с простой синхронизацией можно освобождать блокировки только в том порядке, в котором они были приобретены.

Также, можно получить блокировку в одном методе, а освободить ее в другом методе, то есть получать блокировку неблочной структуры.

Это гибкость также может создать и проблему.

При использовании простой синхронизации невозможно забыть снять блокировку, JVM сделает это за вас, когда вы выйдете из блока synchronized.

Но при использовании интерфейса Lock можно забыть снять блокировку.

При этом ваша программа пройдёт все тесты и зависнет во время работы.

Класс ReentrantLock является наиболее используемой реализацией интерфейса Lock.

Этот класс добавляет дополнительную опцию справедливости.

Конструктор этого класса принимает необязательный параметр fairness.

Когда установлено true, при конкуренции нескольких потоков для получения блокировки, доступ предоставляется самому долго ожидающему потоку.

В противном случае блокировка не гарантирует какой-либо конкретный порядок доступа.

Простая синхронизация также не гарантирует какой-либо конкретный порядок получения блокировки для нескольких конкурирующих потоков.

В качестве примера, в случае простой синхронизации, у нас есть класс, имеющий синхронизированный блок, в котором производится декремент числа.

Как модифицировать этот код с использованием ReentrantLock.

Здесь мы создаем объект ReentrantLock, устанавливаем блокировку, выполняем операцию и производим разблокировку.

Метод newCondition класса ReentrantLock позволяет создавать несколько условий в одной блокировке для координации потоков.

В этом примере производителя-потребителя мы создаем два условия для одной блокировки.

И у нас есть два метода для записи и чтения данных.

При записи данных мы устанавливаем блокировку и ждем, пока другой поток их не считает.

Как только другой поток считал данные мы просыпаемся и записываем данные.

После этого будим другие потоки для считывания данных.

Интерфейс ReadWriteLock — это усовершенствованный механизм блокировки потоков, который позволяет нескольким потокам одновременно читать определенный ресурс, но только одному потоку записывать одновременно.

Идея состоит в том, что несколько потоков могут считывать общий ресурс без возникновения ошибок параллелизма.

Так как ошибки параллелизма возникают в первую очередь при одновременном считывании и записи в общий ресурс или при одновременном выполнении нескольких записей в общий ресурс.

А здесь запись ограничивается одним потоком.

Правила, по которым потоку разрешено блокировать ReadWriteLock для чтения или записи защищенного ресурса, заключаются в следующем:

Можно блокировать для чтения, если никакие потоки не заблокировали ReadWriteLock для записи, и ни один поток не запросил блокировку записи, но еще не получил ее.

Таким образом, несколько потоков могут удерживать блокировку для чтения.

Можно блокировать для записи, если ни один поток не читает или не записывает.

Таким образом, только один поток может удерживать блокировку для записи.

ReadWriteLock — это интерфейс.

Таким образом, для использования ReadWriteLock пакет java.util.concurrent. locks предоставляет реализацию ReadWriteLock — класс ReentrantReadWriteLock.

Здесь показан простой пример кода, демонстрирующий, как создать ReadWriteLock и как его заблокировать для чтения и записи.

Надо заметить, что ReadWriteLock фактически внутренне хранит два экземпляра Lock. Один защищенный доступ для чтения и один защитный доступ для записи.

Таким образом, иногда нам нужна отдельная блокировка для доступа к чтению и записи.

И для этого Java представляет класс ReentrantReadWriteLock в пакете java.util.concurrent. locks, который обеспечивает такой механизм блокировки.

В политике блокировки он позволяет блокировать чтение одновременно несколькими потоками-читателями, если нет потока-писателя, и блокировка записи является исключительной.

Но позже выяснилось, что у ReentrantReadWriteLock есть серьезные проблемы с голоданием, если они не обрабатываются должным образом.

Использование опции справедливости может помочь, но использование справедливости значительно уменьшает пропускную способность при наличии многих потоков.

Таким образом, поток писателя может попасть в голодание при большом количестве потоков-читателей, а применение для него опции справедливости значительно уменьшит пропускную способность.

В Java 8 добавлен новый тип блокировки StampedLock, который помимо предоставления отдельных блокировок для чтения и записи также имеет функцию оптимистической блокировки для операций чтения.

StampedLock также предоставляет метод обновления блокировки чтения до блокировки записи, которого нет в ReentrantReadWriteLock.

Методы блокировки StampedLock возвращают штамп, представленный значением long.

Вы можете использовать эти штампы, чтобы либо отпустить блокировку, чтобы проверить, является ли блокировка действительной, чтобы преобразовать блокировку.

Если штамп всегда равен нулю, это означает, что не удалось получить доступ к ресурсу.

Имейте в виду, что в StampedLock каждый вызов для получения блокировки всегда возвращает новый штамп и производится блокировка, даже если тот же поток уже содержит блокировку, что может привести к deadlock.

И еще одно замечание — ReadWriteLock имеет два режима управления доступом для чтения и записи, в то время как StampedLock имеет три режима доступа.

При чтении, метод readLock получает неэксклюзивную блокировку. Этот метод возвращает штамп, который можно использовать для разблокировки или преобразования режима.

При записи, метод writeLock получает исключительную блокировку. Этот метод возвращает штамп, который можно использовать для разблокировки или преобразования режима.

Этот метод не будет блокировать и возвращает штамп как ноль, если блокировка не будет немедленно доступна.

Когда блокировка удерживается в режиме записи, никакие блокировки чтения не могут быть получены.

При оптимистическом чтении, метод tryOptimisticRead возвращает ненулевой штамп только в том случае, если в настоящее время блокировка не удерживается в режиме записи.

При этом используется метод validate для проверки правильности значений, считанных при оптимистичном чтении.

Метод validate возвращает true, если блокировка не была запрошена для записи с момента получения данного штампа.

Может возникнуть ситуация, когда вы приобрели блокировку записи и что-то записали, и вы хотели бы прочитать в том же критическом разделе.

Таким образом, чтобы не нарушать потенциальный параллельный доступ, мы можем использовать метод tryConvertToReadLock для конвертации блокировки и получения доступа для чтения.

Теперь предположим, что вы приобрели блокировку чтения, и после успешного чтения вы хотели изменить значение.

Для этого вам понадобится блокировка записи, которую вы можете получить с помощью метода tryConvertToWriteLock.

Следует отметить, что методы

1 2 3 4 5 6 7 8 9 10 ... 15
Перейти на страницу:
На этой странице вы можете бесплатно читать книгу Многопоточное программирование в Java - Тимур Машнин.
Комментарии